
// <copyright file="Impersonator.cs" company="Subhadra Software Solutions">
//   
// </copyright>
// <summary>
//   The impersonator.
// </summary>

namespace SubhadraSolutions.Sharp.Utils.Security
{
    using System;
    using System.ComponentModel;
    using System.Runtime.InteropServices;
    using System.Security.Principal;

    /// <summary>
    /// The impersonator.
    /// </summary>
    public sealed class Impersonator : AbstractDisposable
    {
        #region Fields

        /// <summary>
        /// The impersonation context.
        /// </summary>
        private WindowsImpersonationContext impersonationContext;

        #endregion Fields

        #region Constructors and Finalizers

        /// <summary>
        /// Initializes a new instance of the <see cref="Impersonator"/> class.
        /// </summary>
        /// <param name="userName">
        /// The user name.
        /// </param>
        /// <param name="domainName">
        /// The domain name.
        /// </param>
        /// <param name="password">
        /// The password.
        /// </param>
        public Impersonator(string userName, string domainName, string password)
        {
            this.impersonateUser(userName, domainName, password);
        }

        #endregion Constructors and Finalizers

        #region Methods

        /// <summary>
        /// The dispose.
        /// </summary>
        /// <param name="disposing">
        /// The disposing.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            this.undoImpersonation();
            if (this.impersonationContext != null)
            {
                this.impersonationContext.Dispose();
            }
        }

        /// <summary>
        /// The log on user.
        /// </summary>
        /// <param name="userName">
        /// The user name.
        /// </param>
        /// <param name="domainName">
        /// The domain name.
        /// </param>
        /// <param name="password">
        /// The password.
        /// </param>
        /// <returns>
        /// The <see cref="IntPtr"/>.
        /// </returns>
        /// <exception cref="Win32Exception">
        /// </exception>
        private static IntPtr logOnUser(string userName, string domainName, string password)
        {
            IntPtr token = IntPtr.Zero;
            if (NativeMethods.LogonUser(userName, domainName, password, NativeMethods.LOGON32_LOGON_INTERACTIVE, NativeMethods.LOGON32_PROVIDER_DEFAULT, ref token) != 0)
            {
                return token;
            }

            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        /// <summary>
        /// The revert to self.
        /// </summary>
        /// <exception cref="Win32Exception">
        /// </exception>
        private static void revertToSelf()
        {
            if (!NativeMethods.RevertToSelf())
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }

        /// <summary>
        /// The create impersonation context.
        /// </summary>
        /// <param name="token">
        /// The token.
        /// </param>
        /// <exception cref="Win32Exception">
        /// </exception>
        private void createImpersonationContext(IntPtr token)
        {
            IntPtr tokenDuplicate = IntPtr.Zero;
            try
            {
                if (NativeMethods.DuplicateToken(token, 2, ref tokenDuplicate) == 0)
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }

                using (var tempWindowsIdentity = new WindowsIdentity(tokenDuplicate))
                {
                    this.impersonationContext = tempWindowsIdentity.Impersonate();
                }
            }
            finally
            {
                if (tokenDuplicate != IntPtr.Zero)
                {
                    NativeMethods.CloseHandle(tokenDuplicate);
                }
            }
        }

        /// <summary>
        /// The impersonate user.
        /// </summary>
        /// <param name="userName">
        /// The user name.
        /// </param>
        /// <param name="domainName">
        /// The domain name.
        /// </param>
        /// <param name="password">
        /// The password.
        /// </param>
        private void impersonateUser(string userName, string domainName, string password)
        {
            revertToSelf();
            IntPtr token = IntPtr.Zero;
            try
            {
                token = logOnUser(userName, domainName, password);
                this.createImpersonationContext(token);
            }
            finally
            {
                if (token != IntPtr.Zero)
                {
                    NativeMethods.CloseHandle(token);
                }
            }
        }

        /// <summary>
        /// The undo impersonation.
        /// </summary>
        private void undoImpersonation()
        {
            if (this.impersonationContext != null)
            {
                this.impersonationContext.Undo();
            }
        }

        #endregion Methods

        /// <summary>
        /// The native methods.
        /// </summary>
        private static class NativeMethods
        {
            #region Fields

            /// <summary>
            /// The logo n 32_ logo n_ interactive.
            /// </summary>
            internal const int LOGON32_LOGON_INTERACTIVE = 2;

            /// <summary>
            /// The logo n 32_ provide r_ default.
            /// </summary>
            internal const int LOGON32_PROVIDER_DEFAULT = 0;

            #endregion Fields

            #region Methods

            /// <summary>
            /// The close handle.
            /// </summary>
            /// <param name="handle">
            /// The handle.
            /// </param>
            /// <returns>
            /// The <see cref="bool"/>.
            /// </returns>
            [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool CloseHandle(IntPtr handle);

            /// <summary>
            /// The duplicate token.
            /// </summary>
            /// <param name="hToken">
            /// The h token.
            /// </param>
            /// <param name="impersonationLevel">
            /// The impersonation level.
            /// </param>
            /// <param name="hNewToken">
            /// The h new token.
            /// </param>
            /// <returns>
            /// The <see cref="int"/>.
            /// </returns>
            [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            internal static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);

            /// <summary>
            /// The logon user.
            /// </summary>
            /// <param name="lpszUserName">
            /// The lpsz user name.
            /// </param>
            /// <param name="lpszDomain">
            /// The lpsz domain.
            /// </param>
            /// <param name="lpszPassword">
            /// The lpsz password.
            /// </param>
            /// <param name="dwLogonType">
            /// The dw logon type.
            /// </param>
            /// <param name="dwLogonProvider">
            /// The dw logon provider.
            /// </param>
            /// <param name="phToken">
            /// The ph token.
            /// </param>
            /// <returns>
            /// The <see cref="int"/>.
            /// </returns>
            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            internal static extern int LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

            /// <summary>
            /// The revert to self.
            /// </summary>
            /// <returns>
            /// The <see cref="bool"/>.
            /// </returns>
            [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool RevertToSelf();

            #endregion Methods
        }
    }
}
